using Engine;
using Engine.Graphics;
using Engine.Input;
using Engine.Media;
using Game;
using GameEntitySystem;
using TemplatesDatabase;

namespace SCIENEW {
    public class SubsystemFluidTransfer : Subsystem, IDrawable, IUpdateable {
        public int[] DrawOrders => [2000];

        public UpdateOrder UpdateOrder => UpdateOrder.Default;

        PrimitivesRenderer3D m_primitivesRenderer3D = new();

        FlatBatch3D m_flatBath;

        FontBatch3D m_textBatch;

        protected float m_remainingSimulationTime;

        public SubsystemTerrain m_subsystemTerrain;

        protected bool m_debugCanDraw;

        /// <summary>
        /// 每个坐标点的流体传输机器（元素）
        /// </summary>
        public Dictionary<Point3, FluidTransferrer> m_elements = new();

        /// <summary>
        /// 每个方块面存在的方块连接点
        /// </summary>
        public Dictionary<CellFace, FluidConnector> m_connectors = new();

        /// <summary>
        /// 所有连接点创建的连接
        /// </summary>
        public List<FluidConnection> m_connections = new();

        /// <summary>
        /// 所有管道的连接
        /// </summary>
        public Dictionary<Point3, PipeFluidConnection> m_pipeConnections = new();

        public override void Load(ValuesDictionary valuesDictionary) {
            base.Load(valuesDictionary);
            m_flatBath = m_primitivesRenderer3D.FlatBatch(0, DepthStencilState.None);
            m_textBatch = m_primitivesRenderer3D.FontBatch(BitmapFont.DebugFont, 0, DepthStencilState.None);
            m_subsystemTerrain = Project.FindSubsystem<SubsystemTerrain>(true);
        }

        #region 设备
        public void OnDeviceGenerated(int x, int y, int z) {
            OnDeviceAdded(x, y, z);
        }

        public void OnDeviceAdded(int x, int y, int z)
        {
            Point3 point = new(x, y, z);
            DeviceBlock? deviceBlock = DevicesUtils.FindDevice(m_subsystemTerrain, point, out int blockValue);
            if (deviceBlock is not IFluidTransferrerDevice fluidTransferrerDevice) return;
            FluidTransferrer? fluidTransferrer = fluidTransferrerDevice.CreateFluidTransferrer(this, m_subsystemTerrain, blockValue, new Point3(x, y, z));
            if (fluidTransferrer == null) return;

            if (m_elements.TryGetValue(point, out FluidTransferrer oldFluidTransferrer)) {
                if (oldFluidTransferrer.GetType() != fluidTransferrer.GetType()) {// transfer不一致，移除旧的并添加新的
                    m_elements.Remove(point);
                    fluidTransferrer.Position = point;
                    m_elements.Add(point, fluidTransferrer);
                    fluidTransferrer.OnAdded();
                }
            }
            else {
                fluidTransferrer.Position = point;
                m_elements.Add(point, fluidTransferrer); //添加新的
                fluidTransferrer.OnAdded();
            }
            UpdateConnectorsAndConnections(point, fluidTransferrerDevice, blockValue);
        }

        public void OnDeviceRemoved(int x, int y, int z) {
            Point3 point = new(x, y, z);
            if (m_elements.ContainsKey(point)) {
                m_elements[point].OnRemoved();
                m_elements.Remove(point); //删除元素
            }
            UpdateConnectorsAndConnections(point, null, -1);
        }

        public void OnDeviceModified(int x, int y, int z) {
            OnDeviceRemoved(x, y, z);
            OnDeviceAdded(x, y, z);
        }


        /// <summary>
        /// 更新指定坐标的连接
        /// device为null，则只删除指定坐标的连接
        /// 否则既删除也添加新的
        /// </summary>
        /// <param name="point"></param>
        /// <param name="device"></param>
        /// <param name="blockValue"></param>
        public void UpdateConnectorsAndConnections(Point3 point, IFluidTransferrerDevice? device, int blockValue) {
            //移除原先的
            Dictionary<CellFace, FluidConnector> cacheConnectors = new(m_connectors);
            foreach (var connector in cacheConnectors) {//删除指定位置的连接口
                if (connector.Key.Point == point) m_connectors.Remove(connector.Key);
            }
            cacheConnectors.Clear();
            List<FluidConnection> cacheConnections = new(m_connections);
            foreach (var connection in cacheConnections) {//连带着连接器一起删除
                if (!m_connectors.ContainsValue(connection.FromConnector)) m_connections.Remove(connection);
                if (!m_connectors.ContainsValue(connection.ToConnector)) m_connections.Remove(connection);
            }
            cacheConnections.Clear();

            //添加新的接口与连接
            for (int i = 0; i < 6; i++) {
                if (device == null) {//移除模式，更新周围管道
                    UpdatePipeGroup(point + CellFace.FaceToPoint3(i));
                    continue;
                }
                //添加模式，更新接口与连接
                FluidConnector? connector = device.GetConnector(m_subsystemTerrain, blockValue, i, point.X, point.Y, point.Z);
                if (connector != null) {
                    m_connectors.Add(connector.MountedFace, connector);

                    //查找相邻面的接口，如果存在则创建连接
                    FluidConnector? neighborConnector = GetNeighborConnector(connector.MountedFace);
                    if (neighborConnector != null) {
                        FluidConnection? fluidConnection = CreateConnection(connector, neighborConnector);
                        if (fluidConnection != null) m_connections.Add(fluidConnection);
                    }
                    else if (GetNeighborPipeConnection(connector.MountedFace, out _)) {//查找相邻面是否有管道连接，有则加入管道连接
                        UpdatePipeGroup(connector.MountedFace.Point + CellFace.FaceToPoint3(connector.MountedFace.Face));
                    }
                }
            }
        }

        /// <summary>
        /// 获取指定面相邻的连接
        /// </summary>
        /// <param name="cellFace"></param>
        /// <returns></returns>
        public FluidConnector? GetNeighborConnector(CellFace cellFace) {
            Point3 point = cellFace.Point + CellFace.FaceToPoint3(cellFace.Face);
            int neighborFace = CellFace.OppositeFace(cellFace.Face);
            CellFace neighborCellFace = new(point.X, point.Y, point.Z, neighborFace);
            if (m_connectors.TryGetValue(neighborCellFace, out FluidConnector fluidConnector)) {
                return fluidConnector;
            }
            return null;
        }

        /// <summary>
        /// 根据俩接口创建连接
        /// </summary>
        /// <param name="connector1"></param>
        /// <param name="connector2"></param>
        public FluidConnection? CreateConnection(FluidConnector connector1, FluidConnector connector2) {
            if (connector1.ConnectorType == connector2.ConnectorType) return null;//类型一致，不创建连接
            float rate = MathUtils.Min(connector1.Rate, connector2.Rate);
            if ((connector1.ConnectorType & FluidConnectorType.Input) != 0
                && (connector2.ConnectorType & FluidConnectorType.Output) != 0) {
                return new FluidConnection(
                    m_elements[connector2.MountedFace.Point],
                    m_elements[connector1.MountedFace.Point],
                    connector2,
                    connector1,
                    rate
                );
            }
            if ((connector1.ConnectorType & FluidConnectorType.Output) != 0
                && (connector2.ConnectorType & FluidConnectorType.Input) != 0) {
                return new FluidConnection(
                    m_elements[connector1.MountedFace.Point],
                    m_elements[connector2.MountedFace.Point],
                    connector1,
                    connector2,
                    rate
                );
            }
            return null;
        }

        /// <summary>
        /// 检测CellFace所对的相邻点位是否有管道
        /// </summary>
        /// <param name="cellFace"></param>
        /// <param name="connection"></param>
        /// <returns></returns>
        public bool GetNeighborPipeConnection(CellFace cellFace, out PipeFluidConnection? connection) {
            Point3 neighborPoint = cellFace.Point + CellFace.FaceToPoint3(cellFace.Face);
            return m_pipeConnections.TryGetValue(neighborPoint, out connection);
        }
        #endregion

        #region 管道
        public void OnPipeGenerated(int x, int y, int z) {
            OnPipeAdded(x, y, z);
        }

        public void OnPipeAdded(int x, int y, int z) {
            UpdatePipeGroup(new Point3(x, y, z));
        }

        public void OnPipeRemoved(int x, int y, int z) {
            //删除移除的管道所在的管道组连接
            Point3 point = new(x, y, z);
            PipeFluidConnection connection = m_pipeConnections[point];
            foreach (var pipeFluidConnection in m_pipeConnections) {
                if (pipeFluidConnection.Value == connection) m_pipeConnections.Remove(pipeFluidConnection.Key);
            }

            //重新为相邻6面生成管道组
            for (int i = 0; i < 6; i++) {
                UpdatePipeGroup(point + CellFace.FaceToPoint3(i));
            }
        }

        public void OnPipeModified(int x, int y, int z) {
            OnPipeRemoved(x, y, z);
            OnPipeAdded(x, y, z);
        }

        void UpdatePipeGroup(Point3 point) {
            HashSet<Point3> pipeGroup = new();

            //确定管道组的流体类型
            IFluidTransferrerPipeDevice? pipeDevice = DevicesUtils.FindDevice(m_subsystemTerrain, point, out int blockValue) as IFluidTransferrerPipeDevice;
            if (pipeDevice == null) return;

            //检测管道组，获得一组连在一起的管道
            GetPipeGroup(point, pipeGroup, pipeDevice.GetTransferringFluidType());

            //删除旧管道对应的连接
            foreach (var pipeGroupPoint in pipeGroup) {
                m_pipeConnections.Remove(pipeGroupPoint);
            }

            //添加新管道对应的连接
            HashSet<FluidConnector> fromConnectors = new();
            HashSet<FluidConnector> toConnectors = new();
            HashSet<FluidTransferrer> fromTransferrers = new();
            HashSet<FluidTransferrer> toTransferrers = new();
            float rate = float.MaxValue;
            foreach (var checkingPoint in pipeGroup) {
                //遍历管道位置周围指定方块
                HashSet<int> faceMask = pipeDevice.GetFaceMask(blockValue);
                for (int face = 0; face < 6; face++) {
                    if (!faceMask.Contains(face)) continue;

                    Point3 connectorPoint = checkingPoint + CellFace.FaceToPoint3(face);
                    int connectorFace = CellFace.OppositeFace(face);
                    if (pipeGroup.Contains(connectorPoint)) continue;//检测连接口的点是管道，继续

                    CellFace connectorCellFace = new(connectorPoint.X, connectorPoint.Y, connectorPoint.Z, connectorFace);
                    if (m_connectors.TryGetValue(connectorCellFace, out FluidConnector connector)) {//找到了连接点
                        if (connector.ConnectorType == FluidConnectorType.Input) {
                            toConnectors.Add(connector);
                            toTransferrers.Add(m_elements[connectorPoint]);
                            rate = MathF.Min(rate, connector.Rate);
                        }
                        else if (connector.ConnectorType == FluidConnectorType.Output) {
                            fromConnectors.Add(connector);
                            fromTransferrers.Add(m_elements[connectorPoint]);
                            rate = MathF.Min(rate, connector.Rate);
                        }
                    }
                }
            }

            //添加管道连接
            PipeFluidConnection pipeFluidConnection = pipeDevice.GetConnection(
                fromTransferrers,
                toTransferrers,
                fromConnectors,
                toConnectors,
                rate,
                pipeDevice.GetTransferringFluidType(),
                this
            );
            foreach (var pipePoint in pipeGroup) {
                m_pipeConnections.Add(pipePoint, pipeFluidConnection);
            }
            pipeFluidConnection.Init();
        }

        void GetPipeGroup(Point3 point, HashSet<Point3> parent, Type acquiredType) {
            if (parent.Contains(point)) return;
            //先检测自己位置
            if (DevicesUtils.FindDevice(m_subsystemTerrain, point, out int blockValue) is IFluidTransferrerPipeDevice pipeDevice && pipeDevice.GetTransferringFluidType() == acquiredType) {
                parent.Add(point);
                HashSet<int> faceMask = pipeDevice.GetFaceMask(blockValue);
                //自己位置有，则检测四周
                for (int i = 0; i < 6; i++) {
                    if (!faceMask.Contains(i)) continue;
                    GetPipeGroup(point + CellFace.FaceToPoint3(i), parent, acquiredType);
                }
            }
        }
        #endregion

        public void Update(float dt) {
            if (Keyboard.IsKeyDownOnce(Key.F3)) m_debugCanDraw = !m_debugCanDraw;

            m_remainingSimulationTime = MathUtils.Min(m_remainingSimulationTime + dt, 0.1f);
            while (m_remainingSimulationTime >= 0.05f) //一秒模拟20次
            {
                //模拟transferrer
                foreach (var transferrer in m_elements) {
                    transferrer.Value.Simulate(m_remainingSimulationTime);
                }
                //模拟Connection
                foreach (var connection in m_connections) {
                    connection.Simulate(m_remainingSimulationTime);
                }
                //模拟管道
                foreach (var pipeConnection in m_pipeConnections) {
                    pipeConnection.Value.Simulate(dt);
                }
                m_remainingSimulationTime -= 0.05f;
            }
        }

        public void Draw(Camera camera, int drawOrder) {
            foreach (var element in m_elements) {
                if (element.Value is IDrawable drawable) drawable.Draw(camera, drawOrder);
            }
            foreach (var pipeConnection in m_pipeConnections) {
                if (pipeConnection.Value is IDrawable drawable) drawable.Draw(camera, drawOrder);
            }

            //绘制debug
            if (!m_debugCanDraw) return;
            foreach (var element in m_elements) {
                m_flatBath.QueueBoundingBox(new BoundingBox(new Vector3(element.Key), new Vector3(element.Key) + Vector3.One), Color.Yellow);
            }
            foreach (var connector in m_connectors) {
                Vector3 pos = new Vector3(connector.Key.Point) + new Vector3(0.5f);
                pos += CellFace.FaceToVector3(connector.Key.Face) * 0.4f;
                Color color = new Color(0, 255, 255);
                switch (connector.Value.ConnectorType) {
                    case FluidConnectorType.Input: color = new Color(0, 255, 0);
                        break;
                    case FluidConnectorType.Output: color = new Color(0, 0, 255);
                        break;
                }
                m_flatBath.QueueBoundingBox(new BoundingBox(pos - Vector3.One * 0.1f, pos + Vector3.One * 0.1f), color);
            }
            foreach (var connection in m_connections) {
                Vector3 fromPos = new Vector3(connection.FromConnector.MountedFace.Point) + new Vector3(0.5f);
                fromPos += CellFace.FaceToVector3(connection.FromConnector.MountedFace.Face) * 0.4f;
                Vector3 toPos = new Vector3(connection.ToConnector.MountedFace.Point) + new Vector3(0.5f);
                toPos += CellFace.FaceToVector3(connection.ToConnector.MountedFace.Face) * 0.4f;
                m_flatBath.QueueLine(fromPos, toPos, Color.Purple, Color.Pink);
            }

            foreach (var pipeConnection in m_pipeConnections) {
                m_flatBath.QueueBoundingBox(new BoundingBox(new Vector3(pipeConnection.Key), new Vector3(pipeConnection.Key) + Vector3.One), Color.Orange);
                var vector = Vector3.Normalize(Vector3.Cross(camera.ViewDirection, Vector3.UnitY));
                Vector3 v = -Vector3.Normalize(Vector3.Cross(vector, camera.ViewDirection));
                float s = 0.006f;
                m_textBatch.QueueText(pipeConnection.Value.GetHashCode() + "\r\n\r\n" + m_pipeConnections.Values.Distinct().Count(), new Vector3(pipeConnection.Key) + new Vector3(0.5f), vector * s, v * s, Color.White, TextAnchor.HorizontalCenter | TextAnchor.VerticalCenter, Vector2.Zero);
            }
            foreach (var pipeFluidConnection in m_pipeConnections.Values.Distinct()) {
                Vector3 center = pipeFluidConnection.CalcAveragePosition();
                Color centerColor = Color.Lerp(Color.Purple, Color.Pink, 0.5f);
                foreach (var fromConnector in pipeFluidConnection.FromConnectors) {
                    Vector3 fromPos = new Vector3(fromConnector.MountedFace.Point) + new Vector3(0.5f);
                    fromPos += CellFace.FaceToVector3(fromConnector.MountedFace.Face) * 0.4f;
                    m_flatBath.QueueLine(fromPos, center, Color.Purple, centerColor);
                }
                foreach (var toConnector in pipeFluidConnection.ToConnectors) {
                    Vector3 toPos = new Vector3(toConnector.MountedFace.Point) + new Vector3(0.5f);
                    toPos += CellFace.FaceToVector3(toConnector.MountedFace.Face) * 0.4f;
                    m_flatBath.QueueLine(center, toPos, centerColor, Color.Pink);
                }

                var vector = Vector3.Normalize(Vector3.Cross(camera.ViewDirection, Vector3.UnitY));
                Vector3 v = -Vector3.Normalize(Vector3.Cross(vector, camera.ViewDirection));
                float s = 0.006f;
                foreach (var fluidTransferrer in pipeFluidConnection.FromTransferrers) {
                    m_textBatch.QueueText(pipeFluidConnection.GetHashCode() + "\r\n\r\n" + m_pipeConnections.Values.Distinct().Count(), new Vector3(fluidTransferrer.Position) + new Vector3(0.5f), vector * s, v * s, Color.White, TextAnchor.HorizontalCenter | TextAnchor.VerticalCenter, Vector2.Zero);
                }
                foreach (var fluidTransferrer in pipeFluidConnection.ToTransferrers) {
                    m_textBatch.QueueText(pipeFluidConnection.GetHashCode() + "\r\n\r\n" + m_pipeConnections.Values.Distinct().Count(), new Vector3(fluidTransferrer.Position) + new Vector3(0.5f), vector * s, v * s, Color.White, TextAnchor.HorizontalCenter | TextAnchor.VerticalCenter, Vector2.Zero);
                }
            }
            m_primitivesRenderer3D.Flush(camera.ViewProjectionMatrix);
        }
    }
}